﻿define(
	[],
	function () {
        
	    (function ($, undefined) {
	        $.widget("step.BaseKOWidget",
			{
			    // application constants
			   
                
			    // Default options.
			    options: {
			        model: undefined, // defining a default model here causes problems with knockoutJS (see _initModelView)
			        modelData: undefined, // setting this is the same as calling applyDataToModel
			        view: undefined, // inheriting widgets should set a default view for this property in thier options (typically from the loaded .html dependency).
			        useParentModel: false
			    },
			    //#region public methods
			    applyDataToModel: function (data) {
			        ko.mapping.fromJS(data, {}, this.options.model);
			    },

			    FlattenModelToData: function () {
			        return ko.mapping.toJS(this.options.model);
			    },

			    // oneShot: bool - true if the listener should be de-registered after the first call to it, i.e. it will only get one event.
			    //AddModelEventListener: function (modelEventName, useThis, listenerFunction, oneShot) {
			    //    if (!(this.options.model.hasOwnProperty(modelEventName))) {
			    //        throw "AddObjectEventListener failed.  KOWidget's model does not contain event " + modelEventName;
			    //    }
			    //    try {
			    //        this.options.model[modelEventName].AddListener(useThis, listenerFunction, oneShot);
			    //    }
			    //    catch (err) {
			    //        throw "AddObjectEventListener failed. KOWidget's model." + modelEventName + "was not able to add event listener.";
			    //    }
			    //},

			    // access the widget's model and/or any of it's properties/methods.
			    // call with no params to get the full model returned.
			    Model: function (/* [modelProperty,] [value] {, [modelProperty,] [value] }* */) {
			        var myModel = this.options.model;
			        if (arguments.length == 0) {
			            return myModel;
			        }

			        if (typeof arguments[0] === "string") {
			            var args = Array.prototype.slice.call(arguments); // must convert arguments to proper array
			            var modelProperties = args.shift();
			            while (args.length) {
			                var value = args.shift();
			                this._setProperty(myModel, modelProperties, value)
			                // this means read only works without write.
			                modelProperties = args.shift();
			            }
			            return myModel[modelProperties];
			        }
			        else {
			            modelProperties = arguments[0];
			            this._setModelData(myModel, modelProperties);
			            return myModel; // nothing else worth returning.
			        }
			    },
			    //#endregion public methods

			    // Deep clones the data into the model.
			    _setModelData: function (model, data) {
			        for (var prop in data) {
			            this._setProperty(model, prop, data[prop])
			        }
			    },

			    // Deep clones the data into the model.
			    _setProperty: function (model, property, data) {
			        this._checkProperty(model, property);

			        // Don't put model[ property ] in a var and work on the var, because
			        // if its a value property you'll just update the var not the model.
			        if (model[property] === null) {
			            // null so just set it.
			            model[property] = data;
			        }
			        else {
			            var type = typeof model[property];
			            if (type === 'object') {
			                // recursively set this sub object
			                this._setModelData(model[property], data);
			            }
			            else if (type === 'function') {
			                // ko variable ( hopefully )
			                model[property].call(model, data);
			            }
			            else {
			                // a normal variable.
			                model[property] = data;
			            }
			        }
			    },

			    _checkProperty: function (model, property) {
			        if (!(model.hasOwnProperty(property))) {
			            throw "KOWidget Model access failed.  KOWidget's model does not contain property " + property + ".";
			        }
			        else if (property.charAt(0) == '_') {
			            throw "KOWidget Model access failed.  KOWidget's model property " + property + " is not intended for external use.";
			        }
			    },

			    _createDefaultModel: function () {
			        // inheriting widgets should override this method and return a default KO observable model here.
			        return {};
			    },

			    _initModelView: function () {
			        // call this near the start of _create method in inherited widgets.
			        // call _initModel, _initView, and _initKO directly instead if inherited widgets has subwidgets and/or advanced initialization.
			        this._initModel();
			        this._initView();
			        this._initKO();

			    },
			    _initModel: function () {
			        // call this near the start of _create method in inherited widgets.

			        // handle options.model differently.
			        // create a new default model for each widget to play nice with knockoutJS;
			        if (this.options.model === undefined) {
			            this.options.model = this._createDefaultModel();
			        }
			        if (this.options.modelData != undefined) {
			            this.applyDataToModel(this.options.modelData);
			        }
			    },
			    _initView: function () {
			        // call this within _create method in inherited widgets (after _initModel but before _initKO).
			        this.element.html(this.options.view);
			    },
			    _initKO: function () {
			        // call this towards the end of _create method in inherited widgets.
			        if (!this.options.useParentModel) {
			            //ko.applyBindings(this.options.model, this.element[0]);
			            ko.applyBindingsToDescendants(this.options.model, this.element[0]);
			            this.handleAppliedBindings();
			        }
			    },

			    _create: function () {
			    },

			    _handleModelViewSetOption: function (key, value) {
			        switch (key) {
			            case "model":
			                this.handleChangingModel(value);
			                this.options.model = value;
			                //ko.applyBindings(value, this.element[0]);
			                ko.applyBindingsToDescendants(this.options.model, this.element[0]);
			                this.handleAppliedBindings();
			                this.options.useParentModel = false;
			                break;
			            case "view":
			                this.element.html(value);
			                if (!this.options.useParentModel) {
			                    //ko.applyBindings(this.options.model, this.element[0]);
			                    ko.applyBindingsToDescendants(this.options.model, this.element[0]);
			                    this.handleAppliedBindings();
			                }
			                break;
			            case "modelData":
			                this.applyDataToModel(value);
			                break;
			        }
			    },

			    handleChangingModel: function (newModel) {
			        // - inheriting widgets should override this method and do any cleanup such as removing event listeners, etc. from the current model,
			        // since it is about to be replaced.
			        // - parent widgets should call this on child widgets
			        // TODO: evaluate if a children array and a util method would help here.
			        // TODO: should we be triggering an event before this handler is called so that widget consumer can detatch any of their model events?
			    },

			    handleAppliedBindings: function () {
			        // inheriting widgets should override this method and do any model bootstraping here, like property-changed event hookups, etc.
			        // - parent widgets should call this on child widgets
			        // TODO: evaluate if a children array and a util method would help here.
			    }

			});
	    })(jQuery);
	    //#region ko bindings
	    //ko.bindingHandlers.bindInParent = {
	    //    init: function (elem, valueAccessor) {
	    //        var applyBindings = ko.utils.unwrapObservable(valueAccessor());
	    //        return { controlsDescendantBindings: !applyBindings };
	    //    }
	    //};

	    //// Should be part of KO, its !visible.
	    //ko.bindingHandlers.hidden = {
	    //    update: function (element, valueAccessor) {
	    //        ko.bindingHandlers.visible.update(element, function () {
	    //            return !ko.utils.unwrapObservable(valueAccessor());
	    //        });
	    //    }
	    //};

	    ////#region  css binding
	    ////teaching knockout-2.1.0 the functionality of knockout-2.2.1
	    //var classesWrittenByBindingKey = '__ko__cssValue';

	    //ko.bindingHandlers.css = {
	    //    'update': function (element, valueAccessor) {
	    //        var value = ko.utils.unwrapObservable(valueAccessor());
	    //        function objectForEach(obj, action) {
	    //            for (var prop in obj) {
	    //                if (obj.hasOwnProperty(prop)) {
	    //                    action(prop, obj[prop]);
	    //                }
	    //            }
	    //        }
	    //        if (typeof value == "object") {
	    //            objectForEach(value, function (className, shouldHaveClass) {
	    //                shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
	    //                ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
	    //            });
	    //        } else {
	    //            value = String(value || ''); // Make sure we don't try to store or set a non-string value
	    //            ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
	    //            element[classesWrittenByBindingKey] = value;
	    //            ko.utils.toggleDomNodeCssClass(element, value, true);
	    //        }
	    //    }
	    //};
	    ////#endregion  css binding

	    //ko.bindingHandlers.jqButton = {
	    //    init: function (elem, valueAccessor) {
	    //        $(elem).button({ nav: false });
	    //    },
	    //    update: function (elem, valueAccessor) {
	    //        var value = valueAccessor();
	    //        if ($.isDefined(value)) {
	    //            if ($.isDefined(value.enable)) {
	    //                $(elem).button('option', 'disabled', !value.enable);
	    //            }
	    //            if ($.isDefined(value.nav)) {
	    //                $(elem).button('option', 'nav', value.nav);
	    //            }
	    //            if ($.isDefined(value.label)) {
	    //                // Default to just a text button.
	    //                var label = value.label;
	    //                if ($.isDefined(value.icon)) {
	    //                    // Our standard way is to insert the icon using an img tag. This gets over the centering issue.
	    //                    label = '<img class="action-button-icon" src="' + mainPage.ImagesDir + value.icon + '" alt=""/><span class="action-button-text">' + label + "</span>";
	    //                }
	    //                $(elem).button('option', 'label', label);
	    //            }
	    //            else if ($.isDefined(value.icon)) {
	    //                // ui button icons use classes and background images. Doing that programtically is tricky, so stick to an img tag.
	    //                var label = '<img class="action-button-icon" src="' + mainPage.ImagesDir + value.icon + '" alt=""/>';
	    //                $(elem).button('option', 'label', label);
	    //            }
	    //            if ($.isDefined(value.selected)) {
	    //                if (value.selected)
	    //                    $(elem).button('holdState');
	    //                else
	    //                    $(elem).button('releaseState');
	    //            }
	    //        }
	    //    }
	    //};

	    //ko.bindingHandlers.date = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var value = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(value)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(value),
		//                formatstring = allBindings.format || 'default';
	    //            var newdate = new Date(valueUnwrapped);
	    //            if (isNaN(newdate) && typeof (valueUnwrapped) === 'string') {
	    //                // Hack to fix data in IE8 and older browers which do not support creating a date from all but the simpliest
	    //                // strings.  Example ISO dates like this do not work:  2013-01-24T18:30:00.000Z
	    //                var parts = valueUnwrapped.split('T');
	    //                var dateparts = parts[0].split('-');
	    //                var timeparts = parts[1].split(':');
	    //                var secparts = timeparts[2].split('.');
	    //                newdate = new Date(dateparts[0], dateparts[1] - 1, dateparts[2], timeparts[0], timeparts[1], secparts[0], 0.0);
	    //                // IE 8 and earlier forces value to local time zone based on computer, so remove adjustment.
	    //                var minsoff = newdate.getTimezoneOffset() * -1 + newdate.getUTCMinutes();
	    //                newdate.setUTCMinutes(minsoff);
	    //            }
	    //            $(elem).text(newdate.format(formatstring));
	    //        }
	    //    }
	    //};

	    //// Converts a timespan string to minutes resolution.
	    //ko.bindingHandlers.timespan =
		//{
		//    update: function (elem, valueAccessor, allBindingsAccessor) {
		//        var value = valueAccessor(), allBindings = allBindingsAccessor();
		//        if ($.isDefined(value)) {
		//            var valueUnwrapped = ko.utils.unwrapObservable(value),
		//                formatstring = allBindings.format,
		//                span;
		//            if (formatstring) {
		//                var times = valueUnwrapped.split(':');
		//                var H = +times[0],
		//                    M = +times[1],
		//                    s = +times[2],
		//                    flags = {
		//                        H: H,
		//                        HH: H.pad(),
		//                        M: M,
		//                        MM: M.pad(),
		//                        s: s,
		//                        ss: s.pad()
		//                    },
		//                    token = /([HhMs])\1?|"[^"]*"|'[^']*'/g;

		//                span = formatstring.replace(token, function ($0) {
		//                    return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		//                });

		//            }
		//            else {
		//                span = valueUnwrapped;
		//            }
		//            $(elem).text(span);
		//        }
		//    }
		//};

	    //ko.bindingHandlers.speed = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var speed = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(speed)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(speed.Value),
		//                formatstring = allBindings.format,
		//                myspeed = new WeatherUnits.Speed(valueUnwrapped, ko.utils.unwrapObservable(speed.Unit));
	    //            $(elem).text(myspeed.toString(formatstring));
	    //        }
	    //    }
	    //};

	    //ko.bindingHandlers.distance = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var distance = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(distance)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(distance.Value),
		//                formatstring = allBindings.format,
		//                mydistance = new WeatherUnits.Distance(valueUnwrapped, ko.utils.unwrapObservable(distance.Unit));
	    //            $(elem).text(mydistance.toString(formatstring));
	    //        }
	    //    }
	    //};

	    //ko.bindingHandlers.temperature = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var temperature = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(temperature)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(temperature.Value),
		//                formatstring = allBindings.format,
		//                mytemperature = new WeatherUnits.Temperature(valueUnwrapped, ko.utils.unwrapObservable(temperature.Unit));
	    //            $(elem).text(mytemperature.toString(formatstring));
	    //        }
	    //    }
	    //};

	    //ko.bindingHandlers.weight = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var weight = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(weight)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(weight.Value),
		//                formatstring = allBindings.format,
		//                myweight = new WeatherUnits.Weight(valueUnwrapped, ko.utils.unwrapObservable(weight.Unit));
	    //            $(elem).text(myweight.toString(formatstring));
	    //        }
	    //    }
	    //};

	    //ko.bindingHandlers.angle = {
	    //    update: function (elem, valueAccessor, allBindingsAccessor) {
	    //        var angle = valueAccessor(), allBindings = allBindingsAccessor();
	    //        if ($.isDefined(angle)) {
	    //            var valueUnwrapped = ko.utils.unwrapObservable(angle.Value),
		//                formatstring = allBindings.format,
		//                myangle = new WeatherUnits.Angle(valueUnwrapped, ko.utils.unwrapObservable(angle.Unit));
	    //            $(elem).text(myangle.toString(formatstring));
	    //        }
	    //    }
	    //};

	    //// https://github.com/SteveSanderson/knockout/wiki/Bindings---class
	    //ko.bindingHandlers['class'] = {
	    //    update: function (element, valueAccessor) {
	    //        if (element['__ko__previousClassValue__']) {
	    //            $(element).removeClass(element['__ko__previousClassValue__']);
	    //        }
	    //        var value = ko.utils.unwrapObservable(valueAccessor());
	    //        $(element).addClass(value);
	    //        element['__ko__previousClassValue__'] = value;
	    //    }
	    //};

	    ////#endregion ko bindings

	    //ko.virtualElements.allowedBindings.bindInParent = true;

	    ////#region ko extenders
	    //ko.extenders.numeric = function (target, precision) {
	    //    //create a writeable computed observable to intercept writes to our observable
	    //    var result = ko.computed({
	    //        read: target,  //always return the original observables value
	    //        write: function (newValue) {
	    //            var current = target();
	    //            var roundingMultiplier = Math.pow(10, precision);
	    //            var newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue);
	    //            var valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

	    //            //only write if it changed
	    //            if (valueToWrite === current) {
	    //                //if the rounded value is the same, but a different value was written, force a notification for the current field
	    //                if (newValue !== current) {
	    //                    target.notifySubscribers(valueToWrite);
	    //                }
	    //            }
	    //            else {
	    //                target(valueToWrite);
	    //            }
	    //        }
	    //    });
	    //    //initialize with current value to make sure it is rounded appropriately
	    //    result(target());

	    //    //return the new computed observable
	    //    return result;
	    //};

	    //ko.extenders.integer = function (target) {
	    //    //create a writeable computed observable to intercept writes to our observable
	    //    var result = ko.computed({
	    //        read: target,  //always return the original observables value
	    //        write: function (newValue) {
	    //            var current = target();
	    //            var valueToWrite = isNaN(newValue) ? 0 : parseInt(+newValue);

	    //            //only write if it changed
	    //            if (valueToWrite === current) {
	    //                //if the rounded value is the same, but a different value was written, force a notification for the current field
	    //                if (newValue !== current) {
	    //                    target.notifySubscribers(valueToWrite);
	    //                }
	    //            }
	    //            else {
	    //                target(valueToWrite);
	    //            }
	    //        }
	    //    });

	    //    //initialize with current value to make sure it is rounded appropriately
	    //    result(target());

	    //    //return the new computed observable
	    //    return result;
	    //};

	    //ko.extenders.limit = function (target, limits) {
	    //    //create a writeable computed observable to intercept writes to our observable
	    //    var result = ko.computed({
	    //        read: target,  //always return the original observables value
	    //        write: function (newValue) {
	    //            var current = target();
	    //            var valueToWrite = newValue;

	    //            //only write if it changed
	    //            if (newValue < limits.lower) {
	    //                valueToWrite = limits.lower;
	    //            }
	    //            else if (newValue > limits.upper) {
	    //                valueToWrite = limits.upper;
	    //            }

	    //            if (valueToWrite === current) {
	    //                //if the rounded value is the same, but a different value was written, force a notification for the current field
	    //                if (newValue !== current) {
	    //                    target.notifySubscribers(valueToWrite);
	    //                }
	    //            }
	    //            else {
	    //                target(valueToWrite);
	    //            }
	    //        }
	    //    });

	    //    //initialize with current value to make sure it is rounded appropriately
	    //    result(target());

	    //    //return the new computed observable
	    //    return result;
	    //};

	    //ko.extenders.flightlevel = function (target) {
	    //    //create a writeable computed observable to intercept writes to our observable
	    //    var result = ko.computed({
	    //        read: target,  //always return the original observables value
	    //        write: function (newValue) {
	    //            var current = target();
	    //            if ($.isNotDefined(newValue) || $.isEmptyString(newValue)) {
	    //                valueToWrite = '';
	    //            }
	    //            else {
	    //                var valueToWrite = isNaN(newValue) ? 0 : parseInt(+newValue);
	    //                var zero = 3 - valueToWrite.toString().length + 1;
	    //                valueToWrite = Array(+(zero > 0 && zero)).join("0") + valueToWrite;
	    //            }

	    //            //only write if it changed
	    //            if (valueToWrite === current) {
	    //                //if the rounded value is the same, but a different value was written, force a notification for the current field
	    //                if (newValue !== current) {
	    //                    target.notifySubscribers(valueToWrite);
	    //                }
	    //            }
	    //            else {
	    //                target(valueToWrite);
	    //            }

	    //        }
	    //    });

	    //    //initialize with current value to make sure it is rounded appropriately
	    //    result(target());

	    //    //return the new computed observable
	    //    return result;
	    //};

	    //// Ensure target matches the given pattern.
	    //// For best use, set "valueUpdate: 'afterkeydown'" in widget's data-bind attribute so that the validation is done after every keystroke.
	    //ko.extenders.matchPattern = function (target, pattern) {
	    //    //create a writeable computed observable to intercept writes to our observable
	    //    var result = ko.computed({
	    //        read: target, //always return the original observables value
	    //        write: function (newValue) {
	    //            if (pattern.test(newValue)) {
	    //                // newValue matches required pattern, accept it.
	    //                target(newValue);
	    //            }
	    //            else {
	    //                // newValue doesn't match required pattern - ensure control is set to old value.
	    //                target.notifySubscribers(target());
	    //            }
	    //        }
	    //    });

	    //    // initialize with current value to make sure it is validated
	    //    result(target());

	    //    //return the new computed observable
	    //    return result;
	    //};

	    //#endregion ko extenders

	    // static members:
	    $.extend($.step.BaseKOWidget,
		{
		    injectCss: function (uniqueId, cssFragment) {
		        // construct css style block and add it to the page's head section
		        var styleBlock = '<style type="text/css" rel="stylesheet" id="' + uniqueId + '">' + cssFragment + '</style>';
		        var cssTestHit = $('head style[id="' + uniqueId + '"]').length;
		        if (cssTestHit == 0) {
		            $('head').append(styleBlock);
		        }
		    },
		    injectCssFile: function (uniqueId, filePath) {
		        // Add full path to get IE 8 to work.
		        var fullPath = window.location.protocol + '//' + window.location.host;
		        fullPath += filePath
		        var styleBlock = '<link type="text/css" rel="stylesheet" id="' + uniqueId + '" href="' + fullPath + '" />\n';
		        var cssTestHit = $('head link[id="' + uniqueId + '"]').length;
		        if (cssTestHit == 0) {
		            $('#MainCSS').after(styleBlock);
		        }
		    }
		});

	    alert("success - BaseKOWidget");
	    return "success - BaseKOWidget";
	}
);
